import * as express from "express";
import { HandlerDecorator, ControllerMetadata, ControllerMethodMetadata } from "./interfaces";
import { METADATA_KEY } from "./constants";
import { OperationObject } from 'swagger-express-middleware'

export function Controller(path: string, ...middleware: express.RequestHandler[]) {
    return function (target: any) {
        let metadata: ControllerMetadata = { path, middleware, target };
        Reflect.defineMetadata(METADATA_KEY.controller, metadata, target);
    };
}

export function All(path: string, ...middleware: express.RequestHandler[]): HandlerDecorator {
    return Method("all", path, ...middleware);
}

export function Get(path: string, ...middleware: express.RequestHandler[]): HandlerDecorator {
    return Method("get", path, ...middleware);
}

export function Post(path: string, ...middleware: express.RequestHandler[]): HandlerDecorator {
    return Method("post", path, ...middleware);
}

export function Put(path: string, ...middleware: express.RequestHandler[]): HandlerDecorator {
    return Method("put", path, ...middleware);
}

export function Patch(path: string, ...middleware: express.RequestHandler[]): HandlerDecorator {
    return Method("patch", path, ...middleware);
}

export function Head(path: string, ...middleware: express.RequestHandler[]): HandlerDecorator {
    return Method("head", path, ...middleware);
}

export function Delete(path: string, ...middleware: express.RequestHandler[]): HandlerDecorator {
    return Method("delete", path, ...middleware);
}

export function Method(method: string, path: string, ...middleware: express.RequestHandler[]): HandlerDecorator {
    return function (target: any, key: string, descriptor: any) {
        let metadata: ControllerMethodMetadata = { path, middleware, method, target, key }
        mergeIntoMethodEntry(target, key, metadata)
        // decorateToAsync(value) // added to support async function --ron 2016-08-05
    };
}

export function Swagger(swagger: any): HandlerDecorator {
    return function (target: any, key: string, descriptor: any) {
        mergeIntoMethodEntry(target, key, { swagger, key })
    }
}

function mergeIntoMethodEntry(target, key, metadata) {
    // console.log(target, key, metadata); // 控制器类，成员方法，metadata
    let metadataList: ControllerMethodMetadata[] = [];
    if (!Reflect.hasOwnMetadata(METADATA_KEY.controllerMethod, target.constructor)) {
        Reflect.defineMetadata(METADATA_KEY.controllerMethod, metadataList, target.constructor);
    } else {
        metadataList = Reflect.getOwnMetadata(METADATA_KEY.controllerMethod, target.constructor);
    }
    let item = metadataList.find(x => x.key === key)
    if (!item) {
        metadataList.push(metadata)
        return
    }
    Object.assign(item, metadata)
}

function decorateToAsync(descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value

    const decoratedMethod: express.RequestHandler = (req: express.Request, res: express.Response, next: express.NextFunction) => {
        (async () => {
            try {
                return await originalMethod(req, res, next)
            }
            catch (e) {
                next(e)
            }
        })()
    }
    descriptor.value = decoratedMethod
}

export function FollowHandler(...middleware: express.ErrorRequestHandler[]): HandlerDecorator {
    return function (target: any, key: string, value: any) {
        mergeIntoMethodEntry(target, key, { followHandler: middleware, key });
    }
}